#include <plc2llvm/TypeSystem/BasicType.h>

namespace plcst {
    // int & real
    template <TypeKind Kind, typename T, int Size>
    class PlainType: public BasicType {
    public:
        using value_type = T;
        explicit PlainType(std::string&& name, T i=T{}): BasicType(std::move(name)), initValue(i) {}

        explicit PlainType(PlainType<Kind, T, Size>* another) : BasicType(another) { }

        T const& getInitValue() const;

        [[nodiscard]] TypeKind getTypeKind() const override;

        [[nodiscard]] int getNBits() const override;

        virtual TypeMsg typeSynthesisForBinaryOperator(ExpressionOperator op, std::shared_ptr<Type> another) override{
            auto anotherTypeKind = another->getTypeKind();
            auto typemachine = TypeMachine::getTypeMachine();
            if(!typemachine.isNumber(anotherTypeKind)){
                return {nullptr, 2};
            }
            switch(op){
                case ExpressionOperator::POWER:
                case ExpressionOperator::MULTI:
                case ExpressionOperator::DIV:
                case ExpressionOperator::MOD:
                case ExpressionOperator::ADD:
                case ExpressionOperator::MINUS:{
                    return this->getLargerType(another);
                }
                case ExpressionOperator::LESS:
                case ExpressionOperator::GREATER:
                case ExpressionOperator::LESSEQUAL:
                case ExpressionOperator::GREATEREQUAL:
                case ExpressionOperator::EQUAL:
                case ExpressionOperator::NOTEQUAL:{
                    auto globScope = ScopeManager::getScopeManager().getGlobalScope();
                    auto BOOLType = globScope->find<Type>("BOOL");

                    auto largerTypeMsg = this->getLargerType(another);
                    int signal = largerTypeMsg.signal;

                    return {BOOLType, signal};
                }
                default:{
                    return {nullptr, 2};
                }
            }
        }

        virtual TypeMsg typeSynthesisForUnaryOperator(ExpressionOperator op) override {
            auto thisShared = ScopeManager::getScopeManager().getGlobalScope()->find<Type>(this->getTypeName());
            switch(op){
                case ExpressionOperator::POSITIVE:
                case ExpressionOperator::NEGATIVE:{
                    return {thisShared, 0};   
                }
                default:{
                    return {nullptr, 2};
                }
            }
        }

    private:
        T initValue;
        static inline int nbits = Size;
        static inline TypeKind type = Kind;

    public:
        virtual TypeMsg getLargerType(std::shared_ptr<Type> another) override {
            auto thisShared = ScopeManager::getScopeManager().getGlobalScope()->find<Type>(this->getTypeName());
            auto thisTypeKind = this->getTypeKind();
            auto anotherTypeKind = another->getTypeKind();
            auto globalScope = ScopeManager::getScopeManager().getGlobalScope();

            if(thisTypeKind == anotherTypeKind){
                return {thisShared, 0};
            }

            TypeMachine& typeMachine = TypeMachine::getTypeMachine();
            if(typeMachine.isSignedInt(thisTypeKind)){ // this = signed int
                if(typeMachine.isSignedInt(anotherTypeKind)){ // another = signed int
                    auto thisNBits = this->getNBits();
                    auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
                    auto anotherNBits = anotherDetail->getNBits();
                    if(thisNBits > anotherNBits){
                        return {thisShared, 0};
                    }else{
                       return {another, 0};
                    }
                }else if(typeMachine.isUnsignedInt(anotherTypeKind) || 
                typeMachine.isReal(anotherTypeKind)){ // another = unsigned int / real
                    switch(thisTypeKind){
                        case TypeKind::SINT:{ // this = sint
                            switch(anotherTypeKind){
                                case TypeKind::USINT:{ // another = usint
                                    auto INTType = globalScope->find<Type>("INT");
                                    return {INTType, 0};
                                }
                                case TypeKind::UINT:{ // another = uint
                                    auto DINTType = globalScope->find<Type>("INT");
                                    return {DINTType, 0};
                                }
                                case TypeKind::UDINT:{ // another = udint
                                    auto LINTType = globalScope->find<Type>("LINT");
                                    return {LINTType, 0};
                                }
                                case TypeKind::ULINT:{ // another = ulint
                                    return {another, 1}; // warnning
                                }
                                case TypeKind::REAL: // another = real
                                case TypeKind::LREAL:{ // another = lreal
                                    return {another, 0};
                                }
                            }
                        }
                        case TypeKind::INT:{ // this = int
                            switch(anotherTypeKind){
                                case TypeKind::USINT:{ // another = usint
                                    return {thisShared, 0};
                                }
                                case TypeKind::UINT:{ // another = uint
                                    auto DINTType = globalScope->find<Type>("DINT");
                                    return {DINTType, 0};
                                }
                                case TypeKind::UDINT:{ // another = udint
                                    auto LINTType = globalScope->find<Type>("LINT");
                                    return {LINTType, 0};
                                }
                                case TypeKind::ULINT:{ // another = ulint
                                    return {another, 1};
                                }
                                case TypeKind::REAL:
                                case TypeKind::LREAL:{
                                    return {another, 0};
                                }
                            }
                        }
                        case TypeKind::DINT:{
                            switch(anotherTypeKind){
                                case TypeKind::USINT: // another = usint
                                case TypeKind::UINT:{ // another = uint
                                    return {thisShared, 0};
                                }
                                case TypeKind::UDINT:{ // another = udint
                                    auto LINTType = globalScope->find<Type>("LINT");
                                    return {LINTType, 0};
                                }
                                case TypeKind::ULINT:{ // another = ulint
                                    return {another, 1};
                                }
                                case TypeKind::REAL:{
                                    auto LREALType = globalScope->find<Type>("LREAL");
                                    return {LREALType, 0};
                                }
                                case TypeKind::LREAL:{
                                    return {another, 0};
                                }
                            }
                        }
                        case TypeKind::LINT:{
                            case TypeKind::USINT: // another = usint
                            case TypeKind::UINT: // another = uint
                            case TypeKind::UDINT:{ // another = udint
                                return {thisShared, 0};
                            }
                            case TypeKind::ULINT:{ // another = ulint
                                return {another, 1};
                            }
                            case TypeKind::REAL:
                            case TypeKind::LREAL:{
                                auto LREALType = globalScope->find<Type>("LREAL");
                                return {LREALType, 1};
                            }
                        }

                    }
                }
            }else if(typeMachine.isUnsignedInt(thisTypeKind)){ //this = unsigned int 
                if(typeMachine.isUnsignedInt(anotherTypeKind)){
                    auto thisNBits = this->getNBits();
                    auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
                    auto anotherNBits = anotherDetail->getNBits();
                    if(thisNBits > anotherNBits){
                        return {thisShared, 0};
                    }else{
                        return {another, 0};
                    }
                }else if(typeMachine.isSignedInt(anotherTypeKind)){
                    auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
                    return anotherDetail->getLargerType(thisShared);
                }else if(typeMachine.isReal(anotherTypeKind)){
                    switch(thisTypeKind){
                        case TypeKind::USINT:
                        case TypeKind::UINT:{
                            return {another, 0};
                        }
                        case TypeKind::UDINT:{
                            auto LREALType = globalScope->find<Type>("LREAL");
                            return {LREALType, 0};
                        }
                        case TypeKind::ULINT:{
                            auto LREALType = globalScope->find<Type>("LREAL");
                            return {LREALType, 1};
                        }
                    }
                }
            }else{ // this = real
                if(typeMachine.isReal(anotherTypeKind)){ // another = real
                    auto thisNBits = this->getNBits();
                    auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
                    auto anotherNBits = anotherDetail->getNBits();
                    if(thisNBits > anotherNBits){// return larger one
                        return {thisShared, 0};
                    }else{
                        return {another, 0};
                    }
                }else if(typeMachine.isUnsignedInt(anotherTypeKind) ||
                typeMachine.isSignedInt(anotherTypeKind)){ // another = signed int or signed int
                    auto anotherDetail = std::dynamic_pointer_cast<BasicType>(another);
                    return anotherDetail->getLargerType(thisShared);
                }
            }
            return {nullptr, 2};
        }

        /// @brief src(type = this) cast to destTy, TODO: only real to real, int to int, uint to uint implemented 
        /// @param destTy target type
        /// @param src src value
        /// @param builder llvm builder
        /// @return llvm value
        virtual llvm::Value* castTo(std::shared_ptr<Type> destTy, llvm::Value* src, llvm::IRBuilder<>* builder) override {
            auto anotherTypeKind = destTy->getTypeKind();
            auto thisTypeKind = this->getTypeKind();
            if(thisTypeKind == anotherTypeKind){
                return src;
            }
            TypeMachine& typeMachine = TypeMachine::getTypeMachine();
            if(typeMachine.isNumber(anotherTypeKind)){
                auto thisNBit = this->getNBits();
                auto anotherDetail = std::dynamic_pointer_cast<BasicType>(destTy);
                auto anotherNBit = anotherDetail->getNBits();
                if(thisNBit > anotherNBit){ // down cast
                    if(typeMachine.isInt(thisTypeKind) && typeMachine.isInt(anotherTypeKind)) {
                        return builder->CreateTrunc(src, destTy->llvmty);
                    }else if(typeMachine.isReal(thisTypeKind) && typeMachine.isReal(anotherTypeKind)){
                        return builder->CreateFPTrunc(src, destTy->llvmty);
                    }else{
                        throw SemanticError("plain cast error");
                        return nullptr;
                    }
                }else{ // upcast
                    if(typeMachine.isSignedInt(thisTypeKind) && typeMachine.isSignedInt(anotherTypeKind)) {
                        return builder->CreateSExt(src, destTy->llvmty);
                    }else if(typeMachine.isUnsignedInt(thisTypeKind) && typeMachine.isUnsignedInt(anotherTypeKind)){
                        return builder->CreateZExt(src, destTy->llvmty);
                    }else if(typeMachine.isReal(thisTypeKind) && typeMachine.isReal(anotherTypeKind)){
                        return builder->CreateFPExt(src, destTy->llvmty);
                    }else{
                        throw SemanticError("plain cast error");
                        return nullptr;
                    }
                }
            }else{
                throw SemanticError("bad date time cast");
                return nullptr;
            }
        }

        virtual std::shared_ptr<Type> clone() override {
            return std::make_shared<PlainType<Kind, T, Size>>(this);
        }
    };

    template<TypeKind Kind, typename T, int Size>
    TypeKind PlainType<Kind, T, Size>::getTypeKind() const {
        return type;
    }

    template<TypeKind Kind, typename T, int Size>
    int PlainType<Kind, T, Size>::getNBits() const {
        return nbits;
    }

    template<TypeKind Kind, typename T, int Size>
    T const& PlainType<Kind, T, Size>::getInitValue() const {
        return PlainType::nbits;
    }

}

